package be.kuleuven.cs.mop.domain.model.impl;


import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import be.kuleuven.cs.mop.domain.exceptions.TaskManagerException;
import be.kuleuven.cs.mop.domain.model.TaskType;
import be.kuleuven.cs.mop.domain.model.UserType;


/**
 * A class representing a TaskType. 
 *
 */
public class TaskTypeImpl extends WorldElement implements TaskType {

	
	private HashMap<String, FieldType>			fieldsTemplate;
	private String								name;
	private HashSet<UserTypeImpl>				ownerUserTypes;
	private HashMap<ResourceTypeImpl, Interval> requiredResourceTypes;
	private HashMap<UserTypeImpl, Interval>		helperUserTypesConstraints;
	

	
	/**
	 * Returns the required resource types.
	 */
	@Override
	public Map<ResourceTypeImpl, Interval> getRequiredResourceTypes() {
		return Collections.unmodifiableMap(requiredResourceTypes);
	}
	
	/**
	 * Returns the constraints on users that can be invited. 
	 */
	@Override
	public Map<UserTypeImpl, Interval> getHelperUserTypesConstraints(){
		return Collections.unmodifiableMap(this.helperUserTypesConstraints);
	}
	
	/**
	 * Returns the a template for the fields in a Task
	 */
	@Override
	public Map<String, FieldType> getFieldsTemplate() {
		return Collections.unmodifiableMap(fieldsTemplate);
	}
	
	/**
	 * Returns the <code>UserTypeImpl</code> that are allowed to create a task of this <code>TaskTypeImpl</code>
	 */
	@Override
	public Collection<UserTypeImpl> getOwnerUserTypes(){
		return Collections.unmodifiableSet(this.ownerUserTypes);
	}


	/**
	 * Constructs a TaskTypeImpl object.
	 * @param world	the World this <code>TaskTypeImpl</code> belongs to
	 * @param name	the name of this <code>TaskTypeImpl</code>
	 * @param ownerUserTypes	a list of <code>UserTypeImpl</code>s that are allowed to create a task of this TaskTypeImpl
	 * @param helperUserTypesConstraints	a Map of <code>UserTypeImpl</code>s and the <code>Interval</code> constraints on that <code>UserTypeImpl</code>'s participation in a task of this TaskType.
	 * @param fields	A Map describing the fields of a Task of this <code>TaskTypeImpl</code>
	 * @param reqResourceTypes	a Map of <code>ResourceTypeImpl</code>s and the <code>Interval</code> requirements of that <code>ResourceTypeImpl</code> in a task of this TaskType.
	 * @throws TaskManagerException {@link TaskTypeImpl#setName(String)}
	 */
	public TaskTypeImpl(final World world, final String name, HashSet<UserTypeImpl> ownerUserTypes, HashMap<UserTypeImpl, Interval> helperUserTypesConstraints, HashMap<String, FieldType> fields, HashMap<ResourceTypeImpl, Interval> reqResourceTypes) throws TaskManagerException {
		super(world);
		setName(name);
		setOwnerUserTypes(ownerUserTypes);
		setFieldsTemplate(fields);
		setRequiredResourceTypes(reqResourceTypes);
		setHelperUserTypesConstraints(helperUserTypesConstraints);
		
		getWorld().add(this);
	}
	
	/**
	 *	Sets the Required <code>ResourceTypeImpl</code>s of this <code>TaskTypeImpl</code> 
	 */
	@SuppressWarnings("unchecked")
	private void setRequiredResourceTypes(
			HashMap<ResourceTypeImpl, Interval> reqResourceTypes) {
		this.requiredResourceTypes = (HashMap<ResourceTypeImpl, Interval>) reqResourceTypes.clone() ;
	}
	
	/**
	 *	Sets the owner <code>UserTypeImpl</code>s of this <code>TaskTypeImpl</code> 
	 */
	@SuppressWarnings("unchecked")
	private void setOwnerUserTypes(HashSet<UserTypeImpl> ownerUserTypes2) {
		this.ownerUserTypes =   (HashSet<UserTypeImpl>) ownerUserTypes2.clone() ;
		
	}


	/**
	 *	Sets the fields template of this <code>TaskTypeImpl</code> 
	 */
	@SuppressWarnings("unchecked")
	private void setFieldsTemplate(HashMap<String, FieldType> fieldsTemplate) {
		this.fieldsTemplate = (HashMap<String, FieldType>) fieldsTemplate.clone() ;
	}
	
	/**
	 *	Sets the constraints on helper users of this <code>TaskTypeImpl</code> 
	 */
	private void setHelperUserTypesConstraints(
			HashMap<UserTypeImpl, Interval> helperUserTypesConstraints) {
		this.helperUserTypesConstraints = helperUserTypesConstraints;
	}
	
	/**
	 *	Returns true if this <code>TaskTypeImpl</code> is the same <code>TaskTypeImpl</code> as the parameter supplied  
	 */
	public boolean equals(Object otherType){
		return (otherType != null) && (otherType instanceof TaskTypeImpl) && (getName().equals(((TaskTypeImpl)otherType).getName()));
	}
	
	/**
	 * Returns the name of this <code>TaskTypeImpl</code>
	 */
	@Override
	public String getName() { return name; }
	
	
	/**
	 * Checks whether the given usertype is allowed to create a task of this type
	 * @return false if this type of task can not be created by this usertype
	 */
	@Override
	public boolean isCreatableBy(final UserType userType) {
		return getOwnerUserTypes().contains(userType);
	}
	
	/**
	 *	Returns true if the fields supplied match the requirements set by this <code>TaskTypeImpl</code> 
	 */
	public boolean matchesFields(Map<String,Field> matchFields){
		final Map<String,FieldType> fieldsTemplate = getFieldsTemplate();
		if (fieldsTemplate.size() != matchFields.size()) {
			return false;
		}
		
		for(final String current : fieldsTemplate.keySet()){
			if (!matchFields.containsKey(current)){
				//Check if both maps contain the key
				return false;
			} else if (!fieldsTemplate.get(current).equals(matchFields.get(current).getType())){
				//Check if the corresponding names have the same FieldType
				return false;
			}
		}
		return true;
	}
	
	/**
	 *	Returns true if the reservations supplied match the requirements set by this <code>TaskTypeImpl</code> 
	 */
	public boolean matchesReservations(Collection<ReservationImpl> reservations){
		HashMap<ResourceTypeImpl, Integer> usedTypes = new HashMap<ResourceTypeImpl, Integer>();
		for(ReservationImpl reservation : reservations){
			ResourceTypeImpl resType = reservation.getResource().getResourceType();
			if(usedTypes.containsKey(resType)){
				Integer temp = usedTypes.get(resType) + 1;
				usedTypes.remove(resType);
				usedTypes.put(resType, temp);
			}
			else{
				usedTypes.put(resType, 1);
				
				//Check if this resource type is required or not, return false if not
				if(!getRequiredResourceTypes().containsKey(resType)){
					return false;
				}
			}
		}
		for(ResourceTypeImpl type : getRequiredResourceTypes().keySet()){
			//Check if every required resource type has been supplied
			if(!usedTypes.containsKey(type)){
				return false;
			}
			//Check if the amount of reservations supplied satisfies the lower and/or upperlimit of required resources of this type.
			if(!getRequiredResourceTypes().get(type).contains((usedTypes.get(type)))){
				return false;
			}
			
		}
		return true;
	}
	
	/**
	 *	Returns true if the helper users (invited users) supplied match the requirements set by this <code>TaskTypeImpl</code> 
	 */
	public boolean matchesHelperUsers(Collection<UserImpl> users){
		HashMap<UserTypeImpl, Integer> usedTypes = new HashMap<UserTypeImpl, Integer>();
		for(UserImpl user : users){
			UserTypeImpl userType = user.getType();
			if(usedTypes.containsKey(userType)){
				Integer temp = usedTypes.get(userType) + 1;
				usedTypes.remove(userType);
				usedTypes.put(userType, temp);
			}
			else{
				usedTypes.put(userType, 1);
				
				//Check if this user type is required or not, return false if not
				if(!getHelperUserTypesConstraints().containsKey(userType)){
					return false;
				}
			}
		}
		for(UserTypeImpl type : getHelperUserTypesConstraints().keySet()){
			//Check if every required user type has been supplied
			if(!usedTypes.containsKey(type)){
				return false;
			}
			//Check if the amount of users supplied satisfies the lower and/or upperlimit of required helper users of this type.
			if(!getHelperUserTypesConstraints().get(type).contains((usedTypes.get(type)))){
				return false;
			}
			
		}
		return true;
	}
	
	/**
	 *	Sets the name of this <code>TaskTypeImpl</code> 
	 *
	 *	@throws TaskManagerException {@link TaskTypeImpl#isValidName(String)}
	 */
	private void setName(final String name) throws TaskManagerException {
		if(!isValidName(name))
			throw new TaskManagerException(TaskManagerException.TASK_TYPE_INVALID_NAME);
		this.name = name;
	}
	
	/**
	 *	Returns true if the name supplied is a valid name for this <code>TaskTypeImpl</code>
	 * 
	 * @pre World must be set
	 */
	private boolean isValidName(String name) {
		return (name != null && !name.isEmpty() && !nameAlreadyInUse(name));
	}
	
	/**
	 * Returns true if this name has already been used for a <code>TaskTypeImpl</code>.
	 */
	private boolean nameAlreadyInUse(String name) {
		for(TaskTypeImpl tType : getWorld().getTaskTypes()) {
			if(tType.getName().equals(name))
				return true;
		}
		return false;
	}
	
	@Override
	public String toString() {
		return getName();
	}
	
}